/* CoralCube | Massively Multiplayer Online Role-Playing Game(MMORPG) Emulator.
|* 
|* Copyright (C) 2008-2010 TrinityCore <http://www.trinitycore.org/>
|*               2008-2010 CoralCube <http://www.CoralCube.com/>
|*
|* This program is free software. You can redistribute it and/or modify it under the terms of 
|* the GNU General Public License as published by the Free Software Foundation, either 
|* version 2 of the License.
|*
|* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY, 
|* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
|* See the GNU General Public License for more details.
|*
|* You should have received a copy of the GNU General Public License along with this program. 
|* If not, see <http://www.gnu.org/licenses/>.
|*/

/* ScriptData
Name: gobject_commandscript
%Complete: 100
Comment: All gobject related commands
Category: commandscripts
EndScriptData */

#include "ScriptMgr.h"
#include "GameEventMgr.h"
#include "ObjectMgr.h"
#include "PoolMgr.h"
#include "MapManager.h"
#include "Chat.h"

class gobject_commandscript : public CommandScript
{
    public:
        gobject_commandscript() : CommandScript("gobject_commandscript") { }

        ChatCommand* GetCommands() const
        {
            static ChatCommand gobjectAddCommandTable[] =
            {
                { "temp",           SEC_GAMEMASTER,     false, &HandleGameObjectAddTempCommand,   "", NULL },
                { "",               SEC_GAMEMASTER,     false, &HandleGameObjectAddCommand,       "", NULL },
                { NULL,             0,                  false, NULL,                              "", NULL }
            };
            static ChatCommand gobjectSetCommandTable[] =
            {
                { "phase",          SEC_GAMEMASTER,     false, &HandleGameObjectSetPhaseCommand,  "", NULL },
                { "state",          SEC_GAMEMASTER,     false, &HandleGameObjectSetStateCommand,  "", NULL },
                { NULL,             0,                  false, NULL,                              "", NULL }
            };
            static ChatCommand gobjectCommandTable[] =
            {
                { "activate",       SEC_GAMEMASTER,     false, &HandleGameObjectActivateCommand,  "", NULL },
                { "delete",         SEC_GAMEMASTER,     false, &HandleGameObjectDeleteCommand,    "", NULL },
                { "info",           SEC_GAMEMASTER,     false, &HandleGameObjectInfoCommand,      "", NULL },
                { "move",           SEC_GAMEMASTER,     false, &HandleGameObjectMoveCommand,      "", NULL },
                { "near",           SEC_GAMEMASTER,     false, &HandleGameObjectNearCommand,      "", NULL },
                { "target",         SEC_GAMEMASTER,     false, &HandleGameObjectTargetCommand,    "", NULL },
                { "turn",           SEC_GAMEMASTER,     false, &HandleGameObjectTurnCommand,      "", NULL },
                { "add",            SEC_GAMEMASTER,     false, NULL,            "", gobjectAddCommandTable },
                { "set",            SEC_GAMEMASTER,     false, NULL,            "", gobjectSetCommandTable },
                { NULL,             0,                  false, NULL,                              "", NULL }
            };
            static ChatCommand commandTable[] =
            {
                { "gobject",        SEC_GAMEMASTER,     false, NULL,                "", gobjectCommandTable },
                { NULL,             0,                  false, NULL,                               "", NULL }
            };
            return commandTable;
        }

        static bool HandleGameObjectActivateCommand(ChatHandler* handler, const char* args)
        {
            if (!*args)
                return false;

            char* cId = handler->extractKeyFromLink((char*)args,"Hgameobject");
            if (!cId)
                return false;

            uint32 lowguid = atoi(cId);
            if (!lowguid)
                return false;

            GameObject* obj = NULL;

            // by DB guid
            if (GameObjectData const* go_data = sObjectMgr.GetGOData(lowguid))
                obj = handler->GetObjectGlobalyWithGuidOrNearWithDbGuid(lowguid,go_data->id);

            if (!obj)
            {
                handler->PSendSysMessage(LANG_COMMAND_OBJNOTFOUND, lowguid);
                handler->SetSentErrorMessage(true);
                return false;
            }

            // Activate
            obj->SetLootState(GO_READY);
            obj->UseDoorOrButton(10000);

            handler->PSendSysMessage("Object activated!");

            return true;
        }

        //spawn go
        static bool HandleGameObjectAddCommand(ChatHandler* handler, const char* args)
        {
            if (!*args)
                return false;

            // number or [name] Shift-click form |color|Hgameobject_entry:go_id|h[name]|h|r
            char* cId = handler->extractKeyFromLink((char*)args,"Hgameobject_entry");
            if (!cId)
                return false;

            uint32 id = atol(cId);
            if (!id)
                return false;

            char* spawntimeSecs = strtok(NULL, " ");

            const GameObjectInfo *gInfo = sObjectMgr.GetGameObjectInfo(id);

            if (!gInfo)
            {
                handler->PSendSysMessage(LANG_GAMEOBJECT_NOT_EXIST,id);
                handler->SetSentErrorMessage(true);
                return false;
            }

            if (gInfo->displayId && !sGameObjectDisplayInfoStore.LookupEntry(gInfo->displayId))
            {
                // report to DB errors log as in loading case
                sLog.outErrorDb("Gameobject (Entry %u GoType: %u) have invalid displayId (%u), not spawned.",id, gInfo->type, gInfo->displayId);
                handler->PSendSysMessage(LANG_GAMEOBJECT_HAVE_INVALID_DATA,id);
                handler->SetSentErrorMessage(true);
                return false;
            }

            Player *chr = handler->GetSession()->GetPlayer();
            float x = float(chr->GetPositionX());
            float y = float(chr->GetPositionY());
            float z = float(chr->GetPositionZ());
            float o = float(chr->GetOrientation());
            Map *map = chr->GetMap();

            GameObject* pGameObj = new GameObject;
            uint32 db_lowGUID = sObjectMgr.GenerateLowGuid(HIGHGUID_GAMEOBJECT);

            if (!pGameObj->Create(db_lowGUID, gInfo->id, map, chr->GetPhaseMaskForSpawn(), x, y, z, o, 0.0f, 0.0f, 0.0f, 0.0f, 0, GO_STATE_READY))
            {
                delete pGameObj;
                return false;
            }

            if (spawntimeSecs)
            {
                uint32 value = atoi((char*)spawntimeSecs);
                pGameObj->SetRespawnTime(value);
                //sLog.outDebug("*** spawntimeSecs: %d", value);
            }

            // fill the gameobject data and save to the db
            pGameObj->SaveToDB(map->GetId(), (1 << map->GetSpawnMode()),chr->GetPhaseMaskForSpawn());

            // this will generate a new guid if the object is in an instance
            if (!pGameObj->LoadFromDB(db_lowGUID, map))
            {
                delete pGameObj;
                return false;
            }

            sLog.outDebug(handler->GetCoreString(LANG_GAMEOBJECT_CURRENT), gInfo->name, db_lowGUID, x, y, z, o);

            map->Add(pGameObj);

            // TODO: is it really necessary to add both the real and DB table guid here ?
            sObjectMgr.AddGameobjectToGrid(db_lowGUID, sObjectMgr.GetGOData(db_lowGUID));

            handler->PSendSysMessage(LANG_GAMEOBJECT_ADD,id,gInfo->name,db_lowGUID,x,y,z);
            return true;
        }

        // add go, temp only
        static bool HandleGameObjectAddTempCommand(ChatHandler* handler, const char* args)
        {
            if (!*args)
                return false;
            char* charID = strtok((char*)args, " ");
            if (!charID)
                return false;

            Player *chr = handler->GetSession()->GetPlayer();

            char* spawntime = strtok(NULL, " ");
            uint32 spawntm = 300;

            if (spawntime)
                spawntm = atoi((char*)spawntime);

            float x = chr->GetPositionX();
            float y = chr->GetPositionY();
            float z = chr->GetPositionZ();
            float ang = chr->GetOrientation();

            float rot2 = sin(ang/2);
            float rot3 = cos(ang/2);

            uint32 id = atoi(charID);

            chr->SummonGameObject(id,x,y,z,ang,0,0,rot2,rot3,spawntm);

            return true;
        }

        static bool HandleGameObjectTargetCommand(ChatHandler* handler, const char* args)
        {
            Player* pl = handler->GetSession()->GetPlayer();
            QueryResult result;
            GameEventMgr::ActiveEvents const& activeEventsList = sGameEventMgr.GetActiveEventList();
            if (*args)
            {
                // number or [name] Shift-click form |color|Hgameobject_entry:go_id|h[name]|h|r
                char* cId = handler->extractKeyFromLink((char*)args,"Hgameobject_entry");
                if (!cId)
                    return false;

                uint32 id = atol(cId);

                if (id)
                    result = WorldDatabase.PQuery("SELECT guid, id, position_x, position_y, position_z, orientation, map, phaseMask, (POW(position_x - '%f', 2) + POW(position_y - '%f', 2) + POW(position_z - '%f', 2)) AS order_ FROM gameobject WHERE map = '%i' AND id = '%u' ORDER BY order_ ASC LIMIT 1",
                        pl->GetPositionX(), pl->GetPositionY(), pl->GetPositionZ(), pl->GetMapId(),id);
                else
                {
                    std::string name = cId;
                    WorldDatabase.escape_string(name);
                    result = WorldDatabase.PQuery(
                        "SELECT guid, id, position_x, position_y, position_z, orientation, map, phaseMask, (POW(position_x - %f, 2) + POW(position_y - %f, 2) + POW(position_z - %f, 2)) AS order_ "
                        "FROM gameobject,gameobject_template WHERE gameobject_template.entry = gameobject.id AND map = %i AND name "_LIKE_" "_CONCAT3_("'%%'","'%s'","'%%'")" ORDER BY order_ ASC LIMIT 1",
                        pl->GetPositionX(), pl->GetPositionY(), pl->GetPositionZ(), pl->GetMapId(),name.c_str());
                }
            }
            else
            {
                std::ostringstream eventFilter;
                eventFilter << " AND (event IS NULL ";
                bool initString = true;

                for (GameEventMgr::ActiveEvents::const_iterator itr = activeEventsList.begin(); itr != activeEventsList.end(); ++itr)
                {
                    if (initString)
                    {
                        eventFilter  <<  "OR event IN (" <<*itr;
                        initString =false;
                    }
                    else
                        eventFilter << "," << *itr;
                }

                if (!initString)
                    eventFilter << "))";
                else
                    eventFilter << ")";

                result = WorldDatabase.PQuery("SELECT gameobject.guid, id, position_x, position_y, position_z, orientation, map, phaseMask, "
                    "(POW(position_x - %f, 2) + POW(position_y - %f, 2) + POW(position_z - %f, 2)) AS order_ FROM gameobject "
                    "LEFT OUTER JOIN game_event_gameobject on gameobject.guid=game_event_gameobject.guid WHERE map = '%i' %s ORDER BY order_ ASC LIMIT 10",
                    handler->GetSession()->GetPlayer()->GetPositionX(), handler->GetSession()->GetPlayer()->GetPositionY(), handler->GetSession()->GetPlayer()->GetPositionZ(), handler->GetSession()->GetPlayer()->GetMapId(),eventFilter.str().c_str());
            }

            if (!result)
            {
                handler->SendSysMessage(LANG_COMMAND_TARGETOBJNOTFOUND);
                return true;
            }

            bool found = false;
            float x, y, z, o;
            uint32 lowguid, id;
            uint16 mapid, phase;
            uint32 pool_id;

            do
            {
                Field *fields = result->Fetch();
                lowguid = fields[0].GetUInt32();
                id =      fields[1].GetUInt32();
                x =       fields[2].GetFloat();
                y =       fields[3].GetFloat();
                z =       fields[4].GetFloat();
                o =       fields[5].GetFloat();
                mapid =   fields[6].GetUInt16();
                phase =   fields[7].GetUInt16();
                pool_id = sPoolMgr.IsPartOfAPool<GameObject>(lowguid);
                if (!pool_id || sPoolMgr.IsSpawnedObject<GameObject>(lowguid))
                    found = true;
            } while (result->NextRow() && (!found));

            if (!found)
            {
                handler->PSendSysMessage(LANG_GAMEOBJECT_NOT_EXIST,id);
                return false;
            }

            GameObjectInfo const* goI = sObjectMgr.GetGameObjectInfo(id);

            if (!goI)
            {
                handler->PSendSysMessage(LANG_GAMEOBJECT_NOT_EXIST,id);
                return false;
            }

            GameObject* target = handler->GetSession()->GetPlayer()->GetMap()->GetGameObject(MAKE_NEW_GUID(lowguid,id,HIGHGUID_GAMEOBJECT));

            handler->PSendSysMessage(LANG_GAMEOBJECT_DETAIL, lowguid, goI->name, lowguid, id, x, y, z, mapid, o, phase);

            if (target)
            {
                int32 curRespawnDelay = int32(target->GetRespawnTimeEx()-time(NULL));
                if (curRespawnDelay < 0)
                    curRespawnDelay = 0;

                std::string curRespawnDelayStr = secsToTimeString(curRespawnDelay,true);
                std::string defRespawnDelayStr = secsToTimeString(target->GetRespawnDelay(),true);

                handler->PSendSysMessage(LANG_COMMAND_RAWPAWNTIMES, defRespawnDelayStr.c_str(),curRespawnDelayStr.c_str());
            }
            return true;
        }

        //delete object by selection or guid
        static bool HandleGameObjectDeleteCommand(ChatHandler* handler, const char* args)
        {
            // number or [name] Shift-click form |color|Hgameobject:go_guid|h[name]|h|r
            char* cId = handler->extractKeyFromLink((char*)args,"Hgameobject");
            if (!cId)
                return false;

            uint32 lowguid = atoi(cId);
            if (!lowguid)
                return false;

            GameObject* obj = NULL;

            // by DB guid
            if (GameObjectData const* go_data = sObjectMgr.GetGOData(lowguid))
                obj = handler->GetObjectGlobalyWithGuidOrNearWithDbGuid(lowguid,go_data->id);

            if (!obj)
            {
                handler->PSendSysMessage(LANG_COMMAND_OBJNOTFOUND, lowguid);
                handler->SetSentErrorMessage(true);
                return false;
            }

            uint64 owner_guid = obj->GetOwnerGUID();
            if (owner_guid)
            {
                Unit* owner = ObjectAccessor::GetUnit(*handler->GetSession()->GetPlayer(),owner_guid);
                if (!owner || !IS_PLAYER_GUID(owner_guid))
                {
                    handler->PSendSysMessage(LANG_COMMAND_DELOBJREFERCREATURE, GUID_LOPART(owner_guid), obj->GetGUIDLow());
                    handler->SetSentErrorMessage(true);
                    return false;
                }

                owner->RemoveGameObject(obj,false);
            }

            obj->SetRespawnTime(0);                                 // not save respawn time
            obj->Delete();
            obj->DeleteFromDB();

            handler->PSendSysMessage(LANG_COMMAND_DELOBJMESSAGE, obj->GetGUIDLow());

            return true;
        }

        //turn selected object
        static bool HandleGameObjectTurnCommand(ChatHandler* handler, const char* args)
        {
            // number or [name] Shift-click form |color|Hgameobject:go_id|h[name]|h|r
            char* cId = handler->extractKeyFromLink((char*)args,"Hgameobject");
            if (!cId)
                return false;

            uint32 lowguid = atoi(cId);
            if (!lowguid)
                return false;

            GameObject* obj = NULL;

            // by DB guid
            if (GameObjectData const* go_data = sObjectMgr.GetGOData(lowguid))
                obj = handler->GetObjectGlobalyWithGuidOrNearWithDbGuid(lowguid,go_data->id);

            if (!obj)
            {
                handler->PSendSysMessage(LANG_COMMAND_OBJNOTFOUND, lowguid);
                handler->SetSentErrorMessage(true);
                return false;
            }

            char* po = strtok(NULL, " ");
            float o;

            if (po)
            {
                o = (float)atof(po);
            }
            else
            {
                Player *chr = handler->GetSession()->GetPlayer();
                o = chr->GetOrientation();
            }

            obj->Relocate(obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ(), o);
            obj->UpdateRotationFields();
            obj->DestroyForNearbyPlayers();
            obj->UpdateObjectVisibility();

            obj->SaveToDB();
            obj->Refresh();

            handler->PSendSysMessage(LANG_COMMAND_TURNOBJMESSAGE, obj->GetGUIDLow(), obj->GetGOInfo()->name, obj->GetGUIDLow(), o);

            return true;
        }

        //move selected object
        static bool HandleGameObjectMoveCommand(ChatHandler* handler, const char* args)
        {
            // number or [name] Shift-click form |color|Hgameobject:go_guid|h[name]|h|r
            char* cId = handler->extractKeyFromLink((char*)args,"Hgameobject");
            if (!cId)
                return false;

            uint32 lowguid = atoi(cId);
            if (!lowguid)
                return false;

            GameObject* obj = NULL;

            // by DB guid
            if (GameObjectData const* go_data = sObjectMgr.GetGOData(lowguid))
                obj = handler->GetObjectGlobalyWithGuidOrNearWithDbGuid(lowguid,go_data->id);

            if (!obj)
            {
                handler->PSendSysMessage(LANG_COMMAND_OBJNOTFOUND, lowguid);
                handler->SetSentErrorMessage(true);
                return false;
            }

            char* px = strtok(NULL, " ");
            char* py = strtok(NULL, " ");
            char* pz = strtok(NULL, " ");

            if (!px)
            {
                Player *chr = handler->GetSession()->GetPlayer();
                obj->Relocate(chr->GetPositionX(), chr->GetPositionY(), chr->GetPositionZ(), obj->GetOrientation());
                obj->DestroyForNearbyPlayers();
                obj->UpdateObjectVisibility();
            }
            else
            {
                if (!py || !pz)
                    return false;

                float x = (float)atof(px);
                float y = (float)atof(py);
                float z = (float)atof(pz);

                if (!MapManager::IsValidMapCoord(obj->GetMapId(),x,y,z))
                {
                    handler->PSendSysMessage(LANG_INVALID_TARGET_COORD,x,y,obj->GetMapId());
                    handler->SetSentErrorMessage(true);
                    return false;
                }

                obj->Relocate(x, y, z, obj->GetOrientation());
                obj->DestroyForNearbyPlayers();
                obj->UpdateObjectVisibility();
            }

            obj->SaveToDB();
            obj->Refresh();

            handler->PSendSysMessage(LANG_COMMAND_MOVEOBJMESSAGE, obj->GetGUIDLow(), obj->GetGOInfo()->name, obj->GetGUIDLow());

            return true;
        }

        //set pahsemask for selected object
        static bool HandleGameObjectSetPhaseCommand(ChatHandler* handler, const char* args)
        {
            // number or [name] Shift-click form |color|Hgameobject:go_id|h[name]|h|r
            char* cId = handler->extractKeyFromLink((char*)args,"Hgameobject");
            if (!cId)
                return false;

            uint32 lowguid = atoi(cId);
            if (!lowguid)
                return false;

            GameObject* obj = NULL;

            // by DB guid
            if (GameObjectData const* go_data = sObjectMgr.GetGOData(lowguid))
                obj = handler->GetObjectGlobalyWithGuidOrNearWithDbGuid(lowguid,go_data->id);

            if (!obj)
            {
                handler->PSendSysMessage(LANG_COMMAND_OBJNOTFOUND, lowguid);
                handler->SetSentErrorMessage(true);
                return false;
            }

            char* phaseStr = strtok (NULL, " ");
            uint32 phasemask = phaseStr? atoi(phaseStr) : 0;
            if (phasemask == 0)
            {
                handler->SendSysMessage(LANG_BAD_VALUE);
                handler->SetSentErrorMessage(true);
                return false;
            }

            obj->SetPhaseMask(phasemask,true);
            obj->SaveToDB();
            return true;
        }

        static bool HandleGameObjectNearCommand(ChatHandler* handler, const char* args)
        {
            float distance = (!*args) ? 10.0f : (float)(atof(args));
            uint32 count = 0;

            Player* pl = handler->GetSession()->GetPlayer();
            QueryResult result = WorldDatabase.PQuery("SELECT guid, id, position_x, position_y, position_z, map, "
                "(POW(position_x - '%f', 2) + POW(position_y - '%f', 2) + POW(position_z - '%f', 2)) AS order_ "
                "FROM gameobject WHERE map='%u' AND (POW(position_x - '%f', 2) + POW(position_y - '%f', 2) + POW(position_z - '%f', 2)) <= '%f' ORDER BY order_",
                pl->GetPositionX(), pl->GetPositionY(), pl->GetPositionZ(),
                pl->GetMapId(),pl->GetPositionX(), pl->GetPositionY(), pl->GetPositionZ(),distance*distance);

            if (result)
            {
                do
                {
                    Field *fields = result->Fetch();
                    uint32 guid = fields[0].GetUInt32();
                    uint32 entry = fields[1].GetUInt32();
                    float x = fields[2].GetFloat();
                    float y = fields[3].GetFloat();
                    float z = fields[4].GetFloat();
                    uint16 mapid = fields[5].GetUInt16();

                    GameObjectInfo const * gInfo = sObjectMgr.GetGameObjectInfo(entry);

                    if (!gInfo)
                        continue;

                    handler->PSendSysMessage(LANG_GO_LIST_CHAT, guid, entry, guid, gInfo->name, x, y, z, mapid);

                    ++count;
                } while (result->NextRow());
            }

            handler->PSendSysMessage(LANG_COMMAND_NEAROBJMESSAGE,distance,count);
            return true;
        }

        //show info of gameobject
        static bool HandleGameObjectInfoCommand(ChatHandler* handler, const char* args)
        {
            uint32 entry = 0;
            uint32 type = 0;
            uint32 displayid = 0;
            std::string name;
            uint32 lootId = 0;

            if (!*args)
            {
                if (WorldObject * obj = handler->getSelectedObject())
                    entry = obj->GetEntry();
                else
                    entry = atoi((char*)args);
            }

            GameObjectInfo const* goinfo = sObjectMgr.GetGameObjectInfo(entry);

            if (!goinfo)
                return false;

            type = goinfo->type;
            displayid = goinfo->displayId;
            name = goinfo->name;
            if (type == GAMEOBJECT_TYPE_CHEST)
                lootId = goinfo->chest.lootId;
            else if (type == GAMEOBJECT_TYPE_FISHINGHOLE)
                lootId = goinfo->fishinghole.lootId;

            handler->PSendSysMessage(LANG_GOINFO_ENTRY, entry);
            handler->PSendSysMessage(LANG_GOINFO_TYPE, type);
            handler->PSendSysMessage(LANG_GOINFO_LOOTID, lootId);
            handler->PSendSysMessage(LANG_GOINFO_DISPLAYID, displayid);
            handler->PSendSysMessage(LANG_GOINFO_NAME, name.c_str());

            return true;
        }
        static bool HandleGameObjectSetStateCommand(ChatHandler* handler, const char* args)
        {
            // number or [name] Shift-click form |color|Hgameobject:go_id|h[name]|h|r
            char* cId = handler->extractKeyFromLink((char*)args, "Hgameobject");
            if (!cId)
                return false;

            uint32 lowguid = atoi(cId);
            if (!lowguid)
                return false;

            GameObject* gobj = NULL;

            if (GameObjectData const* goData = sObjectMgr.GetGOData(lowguid))
                gobj = handler->GetObjectGlobalyWithGuidOrNearWithDbGuid(lowguid, goData->id);

            if (!gobj)
            {
                handler->PSendSysMessage(LANG_COMMAND_OBJNOTFOUND, lowguid);
                handler->SetSentErrorMessage(true);
                return false;
            }

            char* ctype = strtok(NULL, " ");
            if (!ctype)
                return false;

            int32 type = atoi(ctype);
            if (type < 0)
            {
                if (type == -1)
                    gobj->SendObjectDeSpawnAnim(gobj->GetGUID());
                else if (type == -2)
                {
                    return false;
                }
                return true;
            }

            char* cstate = strtok(NULL, " ");
            if (!cstate)
                return false;

            int32 state = atoi(cstate);

            if (type < 4)
                gobj->SetByteValue(GAMEOBJECT_BYTES_1, type, state);
            else if (type == 4)
            {
                WorldPacket data(SMSG_GAMEOBJECT_CUSTOM_ANIM,8+4);
                data << gobj->GetGUID();
                data << (uint32)(state);
                gobj->SendMessageToSet(&data, true);
            }
            handler->PSendSysMessage("Set gobject type %d state %d", type, state);

            return true;
        }
};

void AddSC_gobject_commandscript()
{
    new gobject_commandscript();
}
